package com.gee.spring.cloud.feign.flow.execute;

import com.gee.spring.cloud.feign.flow.exception.SkippedException;
import com.gee.spring.cloud.feign.flow.execute.nextwork.NextWorkWrapper;
import com.gee.spring.cloud.feign.flow.handler.WorkHandler;
import com.gee.spring.cloud.feign.flow.work.AbstractWork;
import com.gee.spring.cloud.feign.flow.work.impl.EndWork;
import com.gee.spring.cloud.feign.flow.work.impl.InitWork;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * desc:
 *
 * @author gee wrote on  2021-01-17 09:21:32
 */
public class WorkExecutor {

    public static <V> ExecuteContext execute(ExecuteContext executeContext, WorkExecuteDesigner designer){

        CompletableFuture<ExecuteContext> completableFuture = getCompletableFuture(designer, executeContext);
        try {
            executeContext = completableFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            EndWork endWork = designer.getEndWork();
            dealAfterException(endWork, executeContext);
        }
        return executeContext;
    }

    private static  void dealAfterException(EndWork endWork, ExecuteContext executeContext) {
        SkippedException skippedException = new SkippedException(WorkHandler.WHOLE_FUTURE_EXCEPTION);
        endWork.trySkipPrevious(skippedException, executeContext);
        executeContext.getGlobalExecuteState().set(3);
        executeContext.setGlobalException(skippedException);
        endWork.getCountDownLatch().countDown();
    }

    public static  ExecuteContext execute(ExecuteContext executeContext, WorkExecuteDesigner workExecuteDesigner
            ,long timeout, TimeUnit timeUnit){
        CompletableFuture<ExecuteContext> completableFuture = getCompletableFuture(workExecuteDesigner,executeContext);
        try {
            executeContext = completableFuture.get(timeout, timeUnit);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            EndWork endWork = workExecuteDesigner.getEndWork();
            dealAfterException(endWork, executeContext);
        }
        return executeContext;
    }

    private static  CompletableFuture<ExecuteContext> getCompletableFuture
            (WorkExecuteDesigner workExecuteDesigner, ExecuteContext executeContext){
        InitWork initWork = workExecuteDesigner.getInitWork();
        EndWork endWork = workExecuteDesigner.getEndWork();
        initExecuteContext(initWork, executeContext);
        CompletableFuture<ExecuteContext> executeContextCompletableFuture = CompletableFuture.completedFuture(executeContext);
        WorkExecuteSupplier<ExecuteContext> workExecuteSupplier = new WorkExecuteSupplier<>(executeContextCompletableFuture);
        endWork.setCountDownLatch(workExecuteSupplier.getCountDownLatch());

        CompletableFuture<ExecuteContext> completableFuture =
                executeContextCompletableFuture.whenCompleteAsync(initWork, executeContext.getExecutor());
        initWork.getWorkExecuteProperties().setCompletableFuture(completableFuture);
        return CompletableFuture.supplyAsync(workExecuteSupplier, executeContext.getExecutor());
    }

    private static void initExecuteContext(AbstractWork work,ExecuteContext executeContext) {
        work.initExecuteContext(executeContext);
        Map nextWorks = work.getWorkExecuteProperties().getNextWorks();
        if (nextWorks.size() > 0){
            nextWorks.forEach((nextWorkId, nextWorkWrapper) ->{
                NextWorkWrapper workWrapper = (NextWorkWrapper) nextWorkWrapper;
                workWrapper.getWork().initExecuteContext(executeContext);
            });
        }
    }

    public static DefaultThreadFactory defaultThreadFactory(){
        return new DefaultThreadFactory();
    }
    public static class DefaultThreadFactory implements ThreadFactory{
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "ThreadFlow-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

    }
}
